import math
import time
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor
import numpy as np
from scipy.ndimage import zoom

from dicom_viewer_be_mock_xiaosai.config import volume_resize_mode, EnumVolumeResizeMode
from dicom_viewer_be_mock_xiaosai.utils.logger import logger
from src.dicom_viewer_be_mock_xiaosai.share import machine_config
from src.dicom_viewer_be_mock_xiaosai.components import CoordTransfer
from src.dicom_viewer_be_mock_xiaosai.config import Scale_Percent_Setting
from src.dicom_viewer_be_mock_xiaosai.loader.DicomTagLoader import DicomTagLoader
from src.dicom_viewer_be_mock_xiaosai.type import EnumViewType, EnumMIPMode


def np_to_hu_np(pixel_array, rescale_slope, rescale_intercept):
    np_pixel = np.asarray(pixel_array, dtype=np.float32)
    hu_pixel_array = np_pixel * rescale_slope + rescale_intercept
    return hu_pixel_array


def vector2_list_to_hu_list(ds_arr, rescale_slope, rescale_intercept):
    hu_list = []
    origin_list = []
    for ds in ds_arr:
        np_dicom_pixel = np.asarray(ds.pixel_array, dtype=np.float32)
        dicom_pixel = np_dicom_pixel * rescale_slope + rescale_intercept
        hu_list.append(dicom_pixel)
        origin_list.append(np_dicom_pixel)
    return hu_list, origin_list


def vector2_list_to_hu_array_vectorized(ds_arr, rescale_slope, rescale_intercept):
    if not ds_arr:
        return np.array([]), np.array([])

    # 一次性提取所有pixel_array并堆叠
    pixel_arrays = [np.asarray(ds.pixel_array, dtype=np.float32) for ds in ds_arr]
    origin_volume = np.stack(pixel_arrays, axis=0)

    # 向量化计算HU值
    hu_volume = origin_volume * rescale_slope + rescale_intercept

    return hu_volume, origin_volume


def volume_to_hu_volume(volume, rescale_slope, rescale_intercept):
    return volume * rescale_slope + rescale_intercept


@dataclass
class SlicePosition:
    # 没有归一化的切片中心坐标
    origin_position = (0, 0, 0)  # voxel坐标下的位置。 0 ~ len - 1
    origin_shape = (0, 0, 0)
    norm_shape = (0, 0, 0)
    # 归一化后的mpr中心小球的位置。也就是切片的点。 默认应该在中心位置。
    physical_position = (0, 0, 0)
    slice_in_screen_position = {  # 切片点在屏幕上的坐标。
        EnumViewType.Ax.value: (0, 0),
        EnumViewType.Sag.value: (0, 0),
        EnumViewType.Cor.value: (0, 0),
    }

    def set_origin_position(self, position):
        self.origin_position = tuple(round(position[i]) for i in range(3))

    def set_physical_position(self, position):
        self.physical_position = position

    def set_origin_shape(self, shape, spacing):
        self.origin_shape = shape
        self.norm_shape = tuple(round(shape[i] * spacing[i]) for i in range(3))

    def get_shape_by_view_type(self, view_type):
        if view_type == EnumViewType.Ax.value:
            return self.origin_shape[1], self.origin_shape[2]
        elif view_type == EnumViewType.Sag.value:
            return self.origin_shape[0], self.origin_shape[2]
        elif view_type == EnumViewType.Cor.value:
            return self.origin_shape[0], self.origin_shape[2]
        elif view_type == EnumViewType.stack.value:
            return self.origin_shape[1], self.origin_shape[2]
        return None

    def get_norm_shape_by_view_type(self, view_type):
        instances, height, width = self.norm_shape
        if view_type == EnumViewType.Ax.value:
            return height, width
        elif view_type == EnumViewType.Sag.value:
            return instances, height
        elif view_type == EnumViewType.Cor.value:
            return instances, width

    def set_slice_in_screen_position(self, view_type, position):
        self.slice_in_screen_position[view_type] = position


def batch_resize_volume(norm_mpr_volume, mpr_resize_factor):
    gpu_open = machine_config['gpu']
    if gpu_open:
        return gpu_resize_volume(norm_mpr_volume, mpr_resize_factor)
    else:
        return cpu_resize_volume(norm_mpr_volume, mpr_resize_factor)


def gpu_resize_volume(volume, mpr_resize_factor):
    import torch
    import torch.nn.functional as F
    input_tensor = torch.from_numpy(volume).float()

    # 如果输入是4D的(batch, depth, height, width)
    # 需要添加channel维度使其变为5D(batch, channel, depth, height, width)
    if input_tensor.dim() == 4:
        input_tensor = input_tensor.unsqueeze(1)

    # 进行插值
    output_tensor = F.interpolate(
        input_tensor,
        scale_factor=mpr_resize_factor,  # 不包括batch维度的缩放
        mode='trilinear',
        align_corners=False
    )

    # 将结果转回CPU的numpy数组
    # 如果之前添加了channel维度，这里要去掉
    result = output_tensor.squeeze(1).cpu().numpy()
    return result



def _resize_with_overlap_optimized(norm_mpr_volume, mpr_resize_factor, cpu_count):
    """
    优化版本的带重叠边界的并行resize方案
    主要优化点：
    1. 减少内存分配和拷贝
    2. 优化数据类型
    3. 改进chunk计算逻辑
    4. 减少不必要的操作
    """
    height, width, depth = norm_mpr_volume.shape

    # 计算目标尺寸
    target_height = int(height * mpr_resize_factor[0])
    target_width = int(width * mpr_resize_factor[1])
    target_depth = int(depth * mpr_resize_factor[2])

    # 优化：使用更高效的重叠计算
    overlap_size = max(8, int(height * 0.02))
    # 预分配输出数组，避免后续concatenate
    output_volume = np.empty((target_height, target_width, target_depth),
                             dtype=norm_mpr_volume.dtype)

    def process_chunk_optimized(args):
        chunk_data, chunk_idx, output_start, output_end, actual_overlap_start, actual_overlap_end = args

        # 直接计算最终输出尺寸，避免后续调整
        output_height = output_end - output_start
        final_resize_factor = (
            output_height / chunk_data.shape[0],
            mpr_resize_factor[1],
            mpr_resize_factor[2]
        )

        # 执行resize
        resized_chunk = zoom(chunk_data, final_resize_factor, order=3, prefilter=False)

        # 处理重叠区域裁剪
        if actual_overlap_start > 0:
            overlap_resized_start = int(actual_overlap_start * final_resize_factor[0])
            resized_chunk = resized_chunk[overlap_resized_start:, :, :]

        if actual_overlap_end > 0:
            overlap_resized_end = int(actual_overlap_end * final_resize_factor[0])
            resized_chunk = resized_chunk[:-overlap_resized_end, :, :]

        # 确保精确尺寸匹配
        current_height = resized_chunk.shape[0]
        if current_height != output_height:
            if current_height > output_height:
                resized_chunk = resized_chunk[:output_height, :, :]
            else:
                # 使用更高效的padding方式
                padding_needed = output_height - current_height
                if padding_needed <= current_height:
                    padding_data = resized_chunk[-padding_needed:, :, :]
                else:
                    # 重复最后一行
                    padding_data = np.repeat(resized_chunk[-1:, :, :], padding_needed, axis=0)
                resized_chunk = np.concatenate([resized_chunk, padding_data], axis=0)

        return resized_chunk, output_start, output_end

    # 优化chunk计算逻辑
    chunks_args = []
    chunk_size = height // cpu_count
    current_output_start = 0

    for i in range(cpu_count):
        # 计算原始数据范围
        start_idx = i * chunk_size
        end_idx = start_idx + chunk_size if i < cpu_count - 1 else height

        # 计算重叠区域
        actual_overlap_start = overlap_size if i > 0 else 0
        actual_overlap_end = overlap_size if i < cpu_count - 1 else 0

        chunk_start = max(0, start_idx - actual_overlap_start)
        chunk_end = min(height, end_idx + actual_overlap_end)

        # 计算输出范围
        if i == cpu_count - 1:
            output_end = target_height
        else:
            output_end = current_output_start + int((end_idx - start_idx) * mpr_resize_factor[0])

        chunk_data = norm_mpr_volume[chunk_start:chunk_end, :, :]

        chunks_args.append((
            chunk_data, i, current_output_start, output_end,
            actual_overlap_start, actual_overlap_end
        ))

        current_output_start = output_end

    # 并行处理
    with ThreadPoolExecutor(max_workers=cpu_count) as executor:
        results = list(executor.map(process_chunk_optimized, chunks_args))

    # 直接写入预分配的数组，避免concatenate
    for resized_chunk, output_start, output_end in results:
        output_volume[output_start:output_end, :, :] = resized_chunk

    logger.info(f'优化版重叠边界并行resize完成，输出shape: {output_volume.shape}')
    return output_volume


def _resize_with_overlap(norm_mpr_volume, mpr_resize_factor, cpu_count):
    """
    带重叠边界的并行resize方案 - 预分配内存优化版本
    """
    height, width, depth = norm_mpr_volume.shape

    # 计算目标尺寸，确保精确匹配
    target_height = int(height * mpr_resize_factor[0])
    target_width = int(width * mpr_resize_factor[1])
    target_depth = int(depth * mpr_resize_factor[2])

    # 预分配输出数组，避免后续concatenate
    output_volume = np.empty((target_height, target_width, target_depth),
                             dtype=norm_mpr_volume.dtype)

    # 计算重叠区域大小
    overlap_size = max(8, int(height * 0.02))

    # 计算每个chunk的目标大小（基于目标尺寸）
    chunk_target_size = target_height // cpu_count

    def process_chunk_with_overlap(args):
        chunk_data, chunk_idx, is_first, is_last, expected_output_size, output_start_idx = args

        # 对chunk进行resize
        resized_chunk = zoom(chunk_data, mpr_resize_factor, order=3)

        # 计算重叠区域在resize后的大小
        overlap_resized = int(overlap_size * mpr_resize_factor[0])

        # 裁剪重叠区域
        if not is_first and not is_last:
            # 中间chunk，裁剪前后重叠
            start_crop = overlap_resized
            end_crop = resized_chunk.shape[0] - overlap_resized
            resized_chunk = resized_chunk[start_crop:end_crop, :, :]
        elif not is_first:
            # 最后一个chunk，只裁剪前面
            start_crop = overlap_resized
            resized_chunk = resized_chunk[start_crop:, :, :]
        elif not is_last:
            # 第一个chunk，只裁剪后面
            end_crop = resized_chunk.shape[0] - overlap_resized
            resized_chunk = resized_chunk[:end_crop, :, :]

        # 直接写入预分配的输出数组
        output_end_idx = output_start_idx + resized_chunk.shape[0]
        output_volume[output_start_idx:output_end_idx, :, :] = resized_chunk

        return chunk_idx, resized_chunk.shape[0]  # 返回chunk索引和实际大小用于验证

    # 准备带重叠的数据块
    chunks_args = []
    current_target_start = 0

    for i in range(cpu_count):
        # 计算原始数据的分块位置
        chunk_size = height // cpu_count
        start_idx = i * chunk_size
        end_idx = start_idx + chunk_size if i < cpu_count - 1 else height

        # 添加重叠区域
        chunk_start = max(0, start_idx - overlap_size)
        chunk_end = min(height, end_idx + overlap_size)

        chunk_data = norm_mpr_volume[chunk_start:chunk_end, :, :]

        # 计算期望的输出尺寸
        if i == cpu_count - 1:
            # 最后一个chunk，确保总尺寸正确
            expected_output_size = target_height - current_target_start
        else:
            expected_output_size = chunk_target_size

        is_first = (i == 0)
        is_last = (i == cpu_count - 1)

        chunks_args.append((chunk_data, i, is_first, is_last, expected_output_size, current_target_start))
        current_target_start += expected_output_size

    # 并行处理
    with ThreadPoolExecutor(max_workers=cpu_count) as executor:
        results = list(executor.map(process_chunk_with_overlap, chunks_args))

    # 验证结果（可选）
    for chunk_idx, actual_size in results:
        logger.debug(f"Chunk {chunk_idx} 处理完成，实际大小: {actual_size}")

    logger.info(f'重叠边界并行resize完成，输出shape: {output_volume.shape}')
    logger.info(f'期望shape: ({target_height}, {target_width}, {target_depth})')
    return output_volume


def _single_thread_resize_volume(norm_mpr_volume, mpr_resize_factor):
    return zoom(norm_mpr_volume, mpr_resize_factor, order=3)


def cpu_resize_volume(norm_mpr_volume, mpr_resize_factor):
    cpu_count = machine_config['cpu_count']
    if volume_resize_mode == EnumVolumeResizeMode.high.value:
        logger.info(f'分配单线程任务计算resize')
        return _single_thread_resize_volume(norm_mpr_volume, mpr_resize_factor)
    elif volume_resize_mode == EnumVolumeResizeMode.fast.value:
        logger.info(f'分配多线程任务计算resize，cpu的数量是：{cpu_count}')
        return _resize_with_overlap(norm_mpr_volume, mpr_resize_factor, cpu_count)
    else:
        raise ValueError('错误的mode')


@dataclass
class MIPData:
    use_mip = False
    mip_mode: EnumMIPMode = EnumMIPMode.mip.value
    mip_val = {
        EnumViewType.Ax.value: 0,  # 表示的是mip模式下。需要在ax轴上计算的物理距离(mm)
        EnumViewType.Sag.value: 0,
        EnumViewType.Cor.value: 0,
    }
    mip_range_in_norm_voxel = {
        EnumViewType.Ax.value: (0, 0),  # mip模式下在每个轴上累计的norm_voxel坐标范围。
        EnumViewType.Sag.value: (0, 0),
        EnumViewType.Cor.value: (0, 0),
    }

    def set_all(self, use_mip, mip_mode, mip_val):
        self.use_mip = use_mip
        self.mip_val = mip_val
        self.mip_mode = mip_mode

    def set_mip_val(self, min_val):
        self.mip_val = min_val

    def set_mip_range_in_norm_voxel(self, val):
        self.mip_range_in_norm_voxel = val


@dataclass
class ViewportTransform:
    scale: float = 1.0  # 缩放比例
    rotation: float = 0.0  # 旋转角度(弧度)
    flip_horizontal: bool = False
    flip_vertical: bool = False
    scale_center = (0, 0)


@dataclass
class ViewAttr:
    origin_index = 0  # 当前在原始图像中的index，从0 开始
    origin_total = 0  # 原始图像中的总数， 总数。
    dicom_size = (0, 0)  # h,w。 dicom像素的大小， 以 320 * 512 * 512的图像为例。是512 * 512
    scaled_size = (0, 0)  # 初始状态下。由normed_size 缩放来满足png_size 的大小后 的大小。
    png_size = (0, 0)  # h,w 前端显示的图片大小
    render_size = (0, 0)  # 在后端为了提升性能，当前端的显示区域较大时将渲染尺寸缩小。
    display_to_render_ratio = 1  # 显示区域与渲染区域的缩放比例。
    padding = 0  # 前端显示的图片中为了dicom影响不是靠边显示，初始化设置的padding值。
    window = None  #  (0, 0)window center  window_width
    origin_transform_matrix = np.identity(3)  # 初始的仿射变换矩阵。主要用于重置。
    transform_matrix = np.identity(3)  # 变换矩阵
    viewport_transform: ViewportTransform = field(default_factory=ViewportTransform)  # 用来记录当前视图所有的变换信息
    mouse_position = (0, 0)  # 最近鼠标在该视图中显示的位置


class VolumeInfo:
    def __init__(self, dicom_tag_info, coord_transfer):
        self.mip_data = MIPData()
        self.origin_volume = None
        self.origin_hu_volume = None

        self.mpr_volume = None
        self.correct_mpr_volume = None
        self.spacing_info = None
        self.dicom_tag_info: DicomTagLoader = dicom_tag_info
        self.view_info = {
            EnumViewType.stack.value: ViewAttr(),
            EnumViewType.Sag.value: ViewAttr(),
            EnumViewType.Cor.value: ViewAttr(),
            EnumViewType.Ax.value: ViewAttr(),
        }
        self.slice_position: SlicePosition = SlicePosition()
        self.coord_transfer: CoordTransfer = coord_transfer

    def set_canvas_size(self, view_type, size):
        self.view_info[view_type].png_size = (int(size[0]), int(size[1]))
        logger.info(f"png size 设置为:{self.view_info[view_type].png_size}")

    def init_origin_volume_by_exist_files(self, ds_arr):
        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']
        hu_volume, origin_volume = vector2_list_to_hu_array_vectorized(ds_arr, rescale_slope, rescale_intercept)
        self.origin_volume = origin_volume
        self.origin_hu_volume = hu_volume
        self.update_index_by_volume()

    def get_hu_volume(self, volume):
        rescale_slope = self.dicom_tag_info.tags['RescaleSlope']
        rescale_intercept = self.dicom_tag_info.tags['RescaleIntercept']
        hu_volume = volume_to_hu_volume(volume, rescale_slope, rescale_intercept)
        return hu_volume

    def update_index_by_volume(self):
        total, _, _ = self.origin_volume.shape
        self.view_info[EnumViewType.stack.value].origin_total = total
        self.view_info[EnumViewType.stack.value].origin_index = total - 1

    def init_norm_volume(self):

        orientation_matrix = self._get_orientation_matrix()
        mpr_volume = self._reorient_volume(orientation_matrix, self.origin_volume)

        mpr_hu_volume = self.get_hu_volume(mpr_volume)
        self.mpr_volume = mpr_hu_volume

        self.correct_mpr_volume = self.correct_volume(mpr_hu_volume)
        self.init_mpr_slice_position()

    def correct_volume(self, norm_mpr_volume):
        spacing = self.spacing_info
        mpr_target_spacing = (1.0, 1.0, 1.0)
        mpr_resize_factor = [old / new for old, new in zip(spacing, mpr_target_spacing)]
        start_time = time.time()
        # 多cpu 4.8秒。
        # 单个cpu 13.5秒。
        resized_mpr_volume = batch_resize_volume(norm_mpr_volume, mpr_resize_factor)
        end_time = time.time()
        print('correct_volume所用时间:', end_time - start_time)
        return resized_mpr_volume

    def init_mpr_slice_position(self):
        origin_shape = tuple(int(x) for x in self.origin_hu_volume.shape)
        origin_slice_position = tuple(int(x / 2) for x in self.origin_hu_volume.shape)
        self.slice_position.set_origin_shape(origin_shape, self.spacing_info)
        self.update_origin_slice_position(origin_slice_position)

    def update_physical_slice_position(self, physical_position):
        self.slice_position.set_physical_position(physical_position)

    def update_origin_slice_position(self, position):
        rounded_voxel_position = tuple(round(position[i]) for i in range(3))
        self.slice_position.set_origin_position(rounded_voxel_position)
        physical_point = self.voxel_to_physical_coordinate(rounded_voxel_position)
        self.update_physical_slice_position(physical_point)
        self.update_mip_range()

    def init_spacing_info(self):
        if self.spacing_info is None:
            pixel_spacing = self.dicom_tag_info.tags['pixel_spacing']
            thickness = self.dicom_tag_info.tags['thickness']
            self.spacing_info = (thickness, pixel_spacing[0], pixel_spacing[1])

    def _get_orientation_matrix(self) -> np.ndarray:
        """
        根据DICOM的ImageOrientationPatient创建方向矩阵
        """
        # 提取行方向向量和列方向向量
        row_vector = np.array(self.dicom_tag_info.tags['image_orientation'][:3])
        col_vector = np.array(self.dicom_tag_info.tags['image_orientation'][3:])

        # 计算切片方向（行向量和列向量的叉积）
        slice_vector = np.cross(row_vector, col_vector)

        # 创建方向矩阵
        orientation_matrix = np.vstack([row_vector, col_vector, slice_vector])
        return orientation_matrix

    def _reorient_volume(self, orientation_matrix, volume) -> np.ndarray:
        """
        重定向体数据到标准解剖方向(LPS: Left, Posterior, Superior)
        """
        patient_position = self.dicom_tag_info.tags['patient_position']
        # 计算仿射变换参数
        cosines = orientation_matrix

        # 根据患者体位进行基本调整
        if patient_position == 'HFS':  # Head First-Supine
            pass
        elif patient_position == 'FFS':  # Feet First-Supine
            volume = np.flip(volume, axis=0)
        elif patient_position == 'HFP':  # Head First-Prone
            volume = np.flip(volume, axis=2)
        elif patient_position == 'FFP':  # Feet First-Prone
            volume = np.flip(volume, axis=(0, 2))

        # 判断是否需要轴交换或翻转
        # 检查每个方向向量与标准方向的夹角
        primary_directions = np.abs(cosines)

        # 找出主方向
        axis_order = np.array([np.argmax(primary_directions[i]) for i in range(3)])
        flips = np.array([1 if cosines[i, axis_order[i]] > 0 else -1 for i in range(3)])
        volume = np.transpose(volume, tuple(axis_order))

        # 应用翻转
        for axis, flip in enumerate(flips):
            if flip < 0:
                volume = np.flip(volume, axis=axis)
        return volume

    def get_current_slice_index(self, view_type):
        if view_type == EnumViewType.stack.value:
            return self.view_info[EnumViewType.stack.value].origin_index
        elif view_type == EnumViewType.Sag.value:
            return self.slice_position.origin_position[1]
        elif view_type == EnumViewType.Cor.value:
            return self.slice_position.origin_position[2]
        elif view_type == EnumViewType.Ax.value:
            return self.slice_position.origin_position[0]

    def get_total_slice(self, view_type):
        if view_type == EnumViewType.stack.value:
            return self.view_info[EnumViewType.stack.value].origin_total
        elif view_type == EnumViewType.Sag.value:
            return self.slice_position.origin_shape[1]
        elif view_type == EnumViewType.Cor.value:
            return self.slice_position.origin_shape[2]
        elif view_type == EnumViewType.Ax.value:
            return self.slice_position.origin_shape[0]

    def init_mpr_view_current_index(self):
        for view_type in [EnumViewType.Ax.value, EnumViewType.Sag.value,
                          EnumViewType.Cor.value]:
            origin_total = self.get_total_slice(view_type)
            if view_type == EnumViewType.stack.value:
                self.set_origin_current_index(0, EnumViewType.stack.value)
            else:
                self.set_origin_current_index(round(origin_total // 2), view_type)

    def init_stack_view_current_index(self):
        origin_total = self.get_total_slice(EnumViewType.stack.value)
        self.set_origin_current_index(origin_total - 1, EnumViewType.stack.value)

    def get_current_dicom_data(self, view_type):
        index = self.get_current_slice_index(view_type)
        if view_type == EnumViewType.stack.value:
            return self.origin_hu_volume[index, :, :]
        correct_volume = self.correct_mpr_volume
        match view_type:
            case EnumViewType.Sag.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Sag.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    target = round(index * self.spacing_info[1])
                    return correct_volume[:, :, target]
                else:
                    volume_section = correct_volume[:, :, math.floor(mip_range[0]):math.floor(mip_range[1])]
                    return self.apply_mip_mode(volume_section, 2)
            case EnumViewType.Cor.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Cor.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    target = round(index * self.spacing_info[2])
                    return correct_volume[:, target, :]
                else:
                    volume_section = correct_volume[:, math.floor(mip_range[0]):math.floor(mip_range[1]), :]
                    return self.apply_mip_mode(volume_section, 1)
            case EnumViewType.Ax.value:
                mip_range = self.mip_data.mip_range_in_norm_voxel[EnumViewType.Ax.value]
                mip_range_scope = abs(mip_range[0] - mip_range[1])
                if self.mip_data.use_mip is False or mip_range_scope <= 1:
                    # todo 这里如果index * self.spacing_info[0]
                    #  得到的是一个小数。该取上一层还是下一层还是平均值？
                    floor_target = math.floor(index * self.spacing_info[0])
                    ceil_target = math.ceil(index * self.spacing_info[0])
                    if floor_target != ceil_target:
                        volume_section = correct_volume[floor_target: ceil_target, :, :]
                        return np.mean(volume_section, 0)
                    else:
                        return correct_volume[floor_target, :, :]
                else:
                    volume_section = correct_volume[math.floor(mip_range[0]): math.floor(mip_range[1]), :, :]
                    return self.apply_mip_mode(volume_section, 0)
        return None

    def apply_mip_mode(self, volume_section, axis):
        mip_mode = self.mip_data.mip_mode
        if mip_mode == EnumMIPMode.mip.value:
            return np.max(volume_section, axis)

        elif mip_mode == EnumMIPMode.min_ip.value:
            return np.min(volume_section, axis)

        elif mip_mode == EnumMIPMode.mean.value:
            return np.mean(volume_section, axis)

        elif mip_mode == EnumMIPMode.sum.value:
            return np.sum(volume_section, axis)
        else:
            raise ValueError("mip mode set error")

    def get_origin_current_total(self, view_type):
        current_origin_index = self.get_current_slice_index(view_type)
        return current_origin_index, self._get_total_index_by_type(view_type)

    def _get_total_index_by_type(self, view_type):
        if view_type == EnumViewType.stack.value:
            return self.view_info[EnumViewType.stack.value].origin_total
        instances, height, width = self.mpr_volume.shape
        total_index = 0
        match view_type:
            case EnumViewType.Ax.value:
                total_index = instances
            case EnumViewType.Sag.value:
                total_index = width
            case EnumViewType.Cor.value:
                total_index = height
        return total_index

    def set_origin_current_index(self, index, view_type):
        origin_slice_position = self.slice_position.origin_position
        match view_type:
            case EnumViewType.Ax.value:
                self.update_origin_slice_position((index, origin_slice_position[1], origin_slice_position[2]))
            case EnumViewType.Sag.value:
                self.update_origin_slice_position((origin_slice_position[0], index, origin_slice_position[2]))
            case EnumViewType.Cor.value:
                self.update_origin_slice_position((origin_slice_position[0], origin_slice_position[1], index))
            case EnumViewType.stack.value:
                self.view_info[EnumViewType.stack.value].origin_index = index

    def get_mpr_all_view_index(self):
        ans_dict = {}
        for each in [EnumViewType.Sag.value, EnumViewType.Cor.value, EnumViewType.Ax.value]:
            ans_dict[each] = {
                "origin_index": self.get_current_slice_index(each),
                "origin_total": self.get_total_slice(each)
            }
        return ans_dict

    # 用来处理前端直接移动cross的情况。
    def update_slice_position(self, view_type, x, y):
        png_x = x
        png_y = y
        current_origin_slice_position = self.slice_position.origin_position  # 320 * 512 * 512
        origin_dicom_position = self.screen_position_to_clipped_voxel_position(view_type, png_x, png_y)
        origin_x, origin_y = origin_dicom_position
        new_origin_slice_position = (0, 0, 0)
        match view_type:
            case EnumViewType.Ax.value:
                new_origin_slice_position = (current_origin_slice_position[0], origin_y, origin_x)
            case EnumViewType.Sag.value:
                new_origin_slice_position = (origin_x, current_origin_slice_position[1], origin_y)
            case EnumViewType.Cor.value:
                new_origin_slice_position = (origin_x, origin_y, current_origin_slice_position[2])
        self.update_origin_slice_position(new_origin_slice_position)

    def get_spacing(self, view_type: EnumViewType):
        match view_type:
            case EnumViewType.Ax.value:
                return self.spacing_info[1], self.spacing_info[2]
            case EnumViewType.Sag.value:
                return self.spacing_info[1], self.spacing_info[0]
            case EnumViewType.Cor.value:
                return self.spacing_info[1], self.spacing_info[0]
            case EnumViewType.stack.value:
                return self.spacing_info[1], self.spacing_info[2]
        return None

    def set_next_slice(self, view_type):
        origin_index = self.get_current_slice_index(view_type)
        origin_total = self.get_total_slice(view_type)

        next_origin_index = origin_index + 1

        if next_origin_index >= origin_total:
            self.set_origin_current_index(0, view_type)
            return False
        else:
            self.set_origin_current_index(next_origin_index, view_type)
            return True

    def set_prev_slice(self, view_type):
        origin_index = self.get_current_slice_index(view_type)
        origin_total = self.get_total_slice(view_type)
        next_origin_index = origin_index - 1
        if next_origin_index <= -1:
            self.set_origin_current_index(origin_total - 1, view_type)
            return False
        else:
            self.set_origin_current_index(next_origin_index, view_type)
            return True

    def set_slice_index(self, view_type, to_index):
        self.set_origin_current_index(to_index, view_type)

    def init_window(self, view_type):
        size_info = self.view_info[view_type]
        if size_info.window is None:
            size_info.window = (self.dicom_tag_info.tags['window_center'],
                                self.dicom_tag_info.tags['window_width'])

    def get_norm_dicom_size(self, view_type):
        if view_type == EnumViewType.stack.value:
            _, height, width = self.origin_volume.shape
            return height, width
        else:
            return self.slice_position.get_norm_shape_by_view_type(view_type)

    def confirm_render_size(self, view_type):
        size_info = self.view_info[view_type]
        full_display_size = size_info.png_size
        dicom_h, dicom_w = self.get_norm_dicom_size(view_type)
        display_h, display_w = full_display_size
        size_info.display_to_render_ratio = 1
        if display_h > dicom_h and display_w > dicom_w:
            scale_h = dicom_h / display_h
            scale_w = dicom_w / display_w
            size_info.display_to_render_ratio = max(scale_w, scale_h)
            size_info.render_size = (round(display_h * size_info.display_to_render_ratio),
                                     round(display_w * size_info.display_to_render_ratio))

            logger.info(f'前端显示区域{full_display_size}较大，'
                        f'dicom的尺寸为：{dicom_h, dicom_w}'
                        f'选择渲染最小尺寸{size_info.render_size}图片的方案, 缩放比例（渲染/视口）：{size_info.display_to_render_ratio} ')

    def set_transform(self, each_view):
        size_info = self.view_info[each_view]
        viewport_transform = self.view_info[each_view].viewport_transform
        self.init_window(each_view)
        size_info.render_size = size_info.png_size
        self.confirm_render_size(view_type=each_view)
        transform_matrix = size_info.transform_matrix
        origin_transform_matrix = size_info.origin_transform_matrix

        render_size = size_info.render_size
        padding = size_info.padding
        display_size = (render_size[0] - padding * 2,
                        render_size[1] - padding * 2)
        dicom_size = self.get_norm_dicom_size(each_view)
        scale_h = display_size[0] / dicom_size[0]
        scale_w = display_size[1] / dicom_size[1]

        scale = min(scale_w, scale_h)
        viewport_transform.scale = scale

        # 计算缩放后的图像大小
        size_info.scaled_size = (
            int(dicom_size[0] * scale),
            int(dicom_size[1] * scale)
        )

        offset_x = (render_size[1] - size_info.scaled_size[1]) // 2
        offset_y = (render_size[0] - size_info.scaled_size[0]) // 2

        new_origin_matrix = np.array([
            [scale, 0, offset_y],
            [0, scale, offset_x],
            [0, 0, 1]
        ])

        transform_matrix = transform_matrix @ np.linalg.inv(origin_transform_matrix) @ new_origin_matrix
        self.set_origin_transform_matrix(each_view, new_origin_matrix)
        self.view_info[each_view].transform_matrix = transform_matrix

    def update_transform(self, view_type):
        self.set_transform(view_type)

    def init_transform_matrix(self, view_type):
        origin_transform_matrix = self.view_info[view_type].origin_transform_matrix
        self.view_info[view_type].transform_matrix = origin_transform_matrix

    def set_origin_transform_matrix(self, view_type, origin_transform_matrix):
        self.view_info[view_type].origin_transform_matrix = origin_transform_matrix

    def update_transform_matrix(self, view_type, new_matrix):
        current_transform_matrix = self.view_info[view_type].transform_matrix
        self.view_info[view_type].transform_matrix = new_matrix @ current_transform_matrix

    def screen_coord_to_origin_voxel(self, view_type, display_y, display_x):
        transform_matrix = self.view_info[view_type].transform_matrix
        origin_voxel_point = self.coord_transfer.screen_to_origin_voxel(view_type, transform_matrix,
                                                                        (display_y, display_x))
        # logger.info(f'屏幕坐标为:{(display_x, display_y)}, 对应的原始坐标为：{origin_voxel_point}, transform_matrix:{transform_matrix}')
        return origin_voxel_point

    def origin_voxel_coord_to_screen_coord(self, view_type, point):
        matrix = self.view_info[view_type].transform_matrix
        screen_point = self.coord_transfer.origin_voxel_to_screen(view_type, point, transform_matrix=matrix)
        return screen_point

    def get_cross_center(self, view_type):
        init_render_cross_center = self.get_current_cross_point(view_type)
        render_cross_center = tuple(
            round(each_coord / self.view_info[view_type].display_to_render_ratio) for each_coord in
            init_render_cross_center)
        # logger.info(f'init_render_cross_center:{init_render_cross_center},render_cross_center: {render_cross_center}, display_to_render_ratio: {self.view_info[view_type].display_to_render_ratio} ')
        return render_cross_center

    def get_current_cross_point(self, view_type):
        vector3_position = self.slice_position.origin_position
        position = (0, 0)
        match view_type:
            case EnumViewType.Ax.value:
                position = (vector3_position[1], vector3_position[2])
            case EnumViewType.Sag.value:
                position = (vector3_position[2], vector3_position[0])
            case EnumViewType.Cor.value:
                position = (vector3_position[1], vector3_position[0])
        screen_position = self.origin_voxel_coord_to_screen_coord(view_type, position)
        self.slice_position.set_slice_in_screen_position(view_type, screen_position)
        return screen_position

    def screen_position_to_clipped_voxel_position(self, view_type, png_y, png_x):
        self.view_info[view_type].mouse_position = (png_y, png_x)
        origin_voxel_position = self.screen_coord_to_origin_voxel(view_type, png_y, png_x)
        total, h, w = self.origin_volume.shape
        clip_min = (0, 0, 0)
        if view_type == EnumViewType.stack.value:
            clip_max = (h - 1, w - 1, total)
        else:
            clip_max = self.slice_position.get_shape_by_view_type(view_type)
        clip_max = tuple(x - 1 for x in clip_max)
        clipped_origin_voxel_position = tuple(
            max(min(x, y_max), y_min) for x, y_min, y_max in zip(origin_voxel_position, clip_min, clip_max)
        )
        return tuple(map(int, clipped_origin_voxel_position))

    def set_transfer_info_window(self, view_type, delta_x, delta_y):
        window = self.view_info[view_type].window
        target_window = (window[0] + delta_y, window[1] + delta_x)
        self.view_info[view_type].window = target_window

    def set_transfer_absolute_window(self, view_type, window_width, window_center):
        target_window = (window_center, window_width)
        self.view_info[view_type].window = target_window

    def transform_viewport_pan(self, view_type, delta_x, delta_y):
        if delta_x != 0 and delta_y != 0:
            matrix = np.array([
                [1, 0, delta_y],
                [0, 1, delta_x],
                [0, 0, 1]
            ])
            self.update_transform_matrix(view_type, matrix)

    def transform_viewport_zoom(self, view_type, new_zoom_factor):
        viewport_transform = self.view_info[view_type].viewport_transform
        render_size = self.view_info[view_type].render_size
        center_y = round(render_size[0] // 2)
        center_x = round(render_size[1] // 2)

        new_zoom_percent = viewport_transform.scale + new_zoom_factor
        clipped_zoom_percent = np.clip(new_zoom_percent, Scale_Percent_Setting[0], Scale_Percent_Setting[1])

        percent = clipped_zoom_percent / viewport_transform.scale
        translate_to_origin = np.array([
            [1, 0, -center_x],
            [0, 1, -center_y],
            [0, 0, 1]
        ], dtype=np.float32)

        zoom_matrix = np.array([
            [percent, 0, 0],
            [0, percent, 0],
            [0, 0, 1]
        ], dtype=np.float32)

        translate_back = np.array([
            [1, 0, center_x],
            [0, 1, center_y],
            [0, 0, 1]
        ], dtype=np.float32)
        viewport_transform.scale = clipped_zoom_percent
        transform_matrix = translate_back @ zoom_matrix @ translate_to_origin
        self.update_transform_matrix(view_type, transform_matrix)

    def transform_viewport_rotate(self, view_type, clockwise: bool, angle):
        image_height, image_width = self.view_info[view_type].render_size
        viewport_transform = self.view_info[view_type].viewport_transform
        sin_val = math.sin(angle / 180 * math.pi)
        cos_val = math.cos(angle / 180 * math.pi)

        delta_x = image_width // 2 - image_width // 2 * cos_val + image_height // 2 * sin_val
        delta_y = image_height // 2 - image_height // 2 * sin_val - image_height // 2 * cos_val

        if clockwise:
            rotate_matrix = np.array([
                [cos_val, sin_val, delta_y],
                [-sin_val, cos_val, delta_x],
                [0, 0, 1]
            ], dtype=np.float32)
            viewport_transform.rotation = viewport_transform.rotation - angle
        else:
            rotate_matrix = np.array([
                [cos_val, -sin_val, delta_x],
                [sin_val, cos_val, delta_y],
                [0, 0, 1]
            ], dtype=np.float32)
            viewport_transform.rotation = viewport_transform.rotation + angle
        self.update_transform_matrix(view_type, rotate_matrix)

    def transform_viewport_flip(self, view_type, flip_x_flag, flip_y_flag):
        viewport_transform = self.view_info[view_type].viewport_transform
        if flip_x_flag:
            viewport_transform.flip_horizontal = not viewport_transform.flip_horizontal
        if flip_y_flag:
            viewport_transform.flip_vertical = not viewport_transform.flip_vertical

        image_height, image_width = self.view_info[view_type].render_size
        flip = np.identity(3)
        if flip_y_flag:
            flip = np.array([
                [-1, 0, image_height],
                [0, 1, 0],
                [0, 0, 1]
            ], dtype=np.float32)
        if flip_x_flag:
            flip = np.array([
                [1, 0, 0],
                [0, -1, image_width],
                [0, 0, 1]
            ], dtype=np.float32)
        self.update_transform_matrix(view_type, flip)

    def get_window_by_viewer_type(self, view_type):
        return self.view_info[view_type].window

    def resize(self, view_type, new_size):
        #  todo
        pass
        # current_png_size = self.view_info[view_type].png_size
        # scale_h = new_size[0] / current_png_size[0]
        # scale_w = new_size[1] / current_png_size[1]
        # scale = min(scale_w, scale_h)
        # if scale < 1:
        #     scale = max(scale_w, scale_h)
        # self.set_canvas_size(view_type, new_size)
        # viewport_transform = self.view_info[view_type].viewport_transform
        # zoom_matrix = np.array([
        #     [scale, 0, 0],
        #     [0, scale, 0],
        #     [0, 0, 1]
        # ], dtype=np.float32)
        # viewport_transform.scale = scale
        # self.update_transform_matrix(view_type, zoom_matrix)

    def reset_mpr_slice(self):
        origin_shape = self.slice_position.origin_shape
        reset_origin_position = tuple(round(origin_shape[i] // 2) for i in range(3))
        self.update_origin_slice_position(reset_origin_position)

    def voxel_to_physical_coordinate(self, voxel_point):
        x = (voxel_point[1], voxel_point[2], voxel_point[0])
        physical_point = self.coord_transfer.origin_voxel_to_physical(x)
        return physical_point

    def update_mip_range(self):
        half_ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value] * 0.5
        half_sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value] * 0.5
        half_cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value] * 0.5
        current_norm_voxel_coord = self.coord_transfer.origin_voxel_to_norm_voxel_vector3(
            self.slice_position.origin_position)
        norm_shape = self.slice_position.norm_shape
        ax_mip_norm_range = (
            max(current_norm_voxel_coord[0] - half_ax_dis, 0),
            min(current_norm_voxel_coord[0] + half_ax_dis, norm_shape[0] - 1)
        )
        sag_mip_norm_range = (
            max(current_norm_voxel_coord[1] - half_sag_dis, 0),
            min(current_norm_voxel_coord[1] + half_sag_dis, norm_shape[1] - 1)
        )
        cor_mip_norm_range = (
            max(current_norm_voxel_coord[2] - half_cor_dis, 0),
            min(current_norm_voxel_coord[2] + half_cor_dis, norm_shape[2] - 1)
        )

        self.mip_data.set_mip_range_in_norm_voxel({
            EnumViewType.Ax.value: ax_mip_norm_range,
            EnumViewType.Sag.value: sag_mip_norm_range,
            EnumViewType.Cor.value: cor_mip_norm_range,
        })

    def set_mip(self, useMIP, mip_mode, mip_val):
        ax_dis = mip_val[EnumViewType.Ax.value]
        sag_dis = mip_val[EnumViewType.Sag.value]
        cor_dis = mip_val[EnumViewType.Cor.value]
        self.mip_data.set_all(useMIP, mip_mode, mip_val)
        self.mip_data.set_mip_val({
            EnumViewType.Ax.value: ax_dis,
            EnumViewType.Sag.value: sag_dis,
            EnumViewType.Cor.value: cor_dis,
        })
        self.update_mip_range()

    def get_mip_info(self, view_type):
        if self.mip_data is None or self.mip_data.use_mip is False:
            return None
        ax_scale = self.view_info[EnumViewType.Ax.value].viewport_transform.scale
        sag_scale = self.view_info[EnumViewType.Sag.value].viewport_transform.scale
        cor_scale = self.view_info[EnumViewType.Cor.value].viewport_transform.scale
        match view_type:
            case EnumViewType.Ax.value:
                sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value]
                cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value]

                # ax 视图中代表 sag 和cor 的线
                sag_screen_dis = sag_dis * ax_scale
                cor_screen_dis = cor_dis * ax_scale

                return {
                    "delta_x": round(sag_screen_dis / 2),
                    "delta_y": round(cor_screen_dis / 2)
                }
            case EnumViewType.Sag.value:
                ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value]
                cor_dis = self.mip_data.mip_val[EnumViewType.Cor.value]
                # sag 视图中 代表 ax 和 cor的线。
                ax_screen_dis = ax_dis * sag_scale
                cor_screen_dis = cor_dis * sag_scale
                return {
                    "delta_y": round(ax_screen_dis / 2),
                    "delta_x": round(cor_screen_dis / 2),
                }
            case EnumViewType.Cor.value:
                ax_dis = self.mip_data.mip_val[EnumViewType.Ax.value]
                sag_dis = self.mip_data.mip_val[EnumViewType.Sag.value]
                sag_screen_dis = sag_dis * cor_scale
                ax_screen_dis = ax_dis * cor_scale
                return {
                    "delta_x": round(sag_screen_dis / 2),
                    "delta_y": round(ax_screen_dis / 2)
                }

    def toggle_next(self, view_type):
        self.set_next_slice(view_type)
